package com.boss.core;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import com.boss.core.Boss;
import com.boss.core.BossConstants;
import com.boss.core.util.BossGlobalUtil;
import com.boss.core.util.BossUtil;
import hudson.Functions;
import hudson.model.Hudson;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.QueryParameter;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import hudson.model.Descriptor;
import hudson.model.User;
import hudson.util.ListBoxModel;

/**
 * Base descriptor class for Boss.
 * The DescriptorImpls' used in Boss extend from it.
 *
 * @author Momin Noor Khan
 */
public abstract class BossDescriptor extends Descriptor<Boss> {
    private static final Logger LOGGER = Logger.getLogger(BossDescriptor.class.getName());

    @Override
    public String getDisplayName() {
        return clazz.getSimpleName();
    }

    /**
     * Get logged-in user id
     * @return
     */
    public String getLoggedInUserId() {
        return User.current().getId();
    }

    /**
     * Check if the logged-in user is admin
     * @return
     */
    public boolean isAdmin() {
        //To-Do: think over the use of new mechanism to check this. For any user as parameter, and not just for logged-in one
        try {
            if (Functions.hasPermission(Hudson.ADMINISTER)) {
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * Check if logged-in user is authorized for all containers
     * @return
     */
    public boolean isLoggedInUserAuthorizedForAllContainers(){
        String loggedInUserId = User.current().getId();
        return BossUtil.isUserAuthorizedForAllContainers(loggedInUserId);
    }

    /**
     * Populates the environment drop down on the form
     * on the basis of logged-in user id from the user profile.
     *
     * @return
     */
    public ListBoxModel doFillEnvironmentItems() {
        return getEnvironmentListBoxModel4User();
    }

    /**
     * Populates the server drop down on the form
     * on the basis of selected environment from the user profile.
     *
     * @param environment
     * @return
     */
    public ListBoxModel doFillServerItems(@QueryParameter String environment) {
        return getServerListBoxModel4User(environment);
    }

    /**
     * Populates the port drop down on the form
     * on the basis of selected server from the user profile.
     *
     * @param server
     * @return
     */
    public ListBoxModel doFillPortItems(@QueryParameter String environment, @QueryParameter String server) {
        return getPortListBoxModel4User(environment, server);
    }

    /**
     * Populates a drop down list model from the specified property section on the basis of the passed property key
     *
     * @param propertySection
     * @param propertyKey
     * @return
     */
    public ListBoxModel getListBoxModel(String propertySection, String propertyKey) {
        return getListBoxModel(propertySection, propertyKey, true, false);
    }

    /**
     * Get list box model
     * @param propertySection
     * @param propertyKey
     * @param isDummyValueRequired
     * @return
     */
    public ListBoxModel getListBoxModel(String propertySection, String propertyKey, boolean isDummyValueRequired) {
        return getListBoxModel(propertySection, propertyKey, isDummyValueRequired, false);
    }

    /**
     * Get environment listbox model for current user
     * @return
     */
    public ListBoxModel getEnvironmentListBoxModel4User() {
        return getEnvironmentListBoxModel4User(true);
    }

    /**
     * Get server listbox model for current user
     * @param environmentName
     * @return
     */
    public ListBoxModel getServerListBoxModel4User(String environmentName) {
        return getServerListBoxModel4User(environmentName, true);
    }

    /**
     * Get port listbox model for current user
     * @param environmentName
     * @param serverName
     * @return
     */
    public ListBoxModel getPortListBoxModel4User(String environmentName, String serverName) {
        return getPortListBoxModel4User(environmentName, serverName, true);
    }

    /**
     * Get environment listbox model for user
     * @param isDummyValueRequired
     * @return
     */
    public ListBoxModel getEnvironmentListBoxModel4User(boolean isDummyValueRequired) {
        ListBoxModel model = new ListBoxModel();
        List<String> resultList = new ArrayList<String>();
        String userName = getLoggedInUserId();

        if (StringUtils.isBlank(userName)) {
            resultList.add(BossConstants.DUMMY_VALUE);
        } else {
            if (isDummyValueRequired) {
                resultList.add(BossConstants.DUMMY_VALUE);
            }

            if (isLoggedInUserAuthorizedForAllContainers()) {
                resultList.addAll(BossGlobalUtil.getAvailableEnvironmentNames());
            } else {
                resultList.addAll(BossGlobalUtil.getAvailableEnvironmentNames4User(userName));
            }
        }

        for (String s : resultList) {
            model.add(String.format("%s", s), s);
        }
        return model;
    }

    /**
     * Get server listbox model for user
     * @param environmentName
     * @param isDummyValueRequired
     * @return
     */
    public ListBoxModel getServerListBoxModel4User(String environmentName, boolean isDummyValueRequired) {
        ListBoxModel model = new ListBoxModel();
        List<String> resultList = new ArrayList<String>();
        String userName = getLoggedInUserId();

        if (StringUtils.isBlank(userName) || StringUtils.isBlank(environmentName) ||
                environmentName.equals(BossConstants.DUMMY_VALUE)) {
            return model;
        } else {
            if (isDummyValueRequired) {
                resultList.add(BossConstants.DUMMY_VALUE);
            }

            if (isLoggedInUserAuthorizedForAllContainers()) {
                resultList.addAll(BossGlobalUtil.getAvailableServerNames(environmentName));
            } else {
                resultList.addAll(BossGlobalUtil.getAvailableServerNames4User(userName, environmentName));
            }
        }

        for (String s : resultList) {
            model.add(String.format("%s", s), s);
        }
        return model;
    }

    /**
     * Get port listbox model for user
     * @param environmentName
     * @param serverName
     * @param isDummyValueRequired
     * @return
     */
    public ListBoxModel getPortListBoxModel4User(String environmentName, String serverName, boolean isDummyValueRequired) {
        ListBoxModel model = new ListBoxModel();
        List<String> resultList = new ArrayList<String>();
        String userName = getLoggedInUserId();

        if (StringUtils.isBlank(userName) || StringUtils.isBlank(environmentName) || StringUtils.isBlank(serverName) ||
                environmentName.equals(BossConstants.DUMMY_VALUE) || serverName.equals(BossConstants.DUMMY_VALUE)) {
            return model;
        } else {
            if (isDummyValueRequired) {
                resultList.add(BossConstants.DUMMY_VALUE);
            }

            if (isLoggedInUserAuthorizedForAllContainers()) {
                resultList.addAll(BossGlobalUtil.getAvailablePortNames(environmentName, serverName));
            } else {
                resultList.addAll(BossGlobalUtil.getAvailablePortNames4User(userName, environmentName, serverName));
            }
        }

        for (String s : resultList) {
            model.add(String.format("%s", s), s);
        }
        return model;
    }

    /**
     * Populates a drop down list model from the specified property section in boss configuration
     * on the basis of the passed property key.
     * If the dummy value in the drop-down is required then isDummyValueRequired must be set to true
     * else set it to false.
     * On the other hand, if the default value is to be populated as the first item then isdefaultFirstRequired must be set to true instead.
     *
     * @param propertySection
     * @param propertyKey
     * @param isDummyValueRequired
     * @param isDefaultFirstRequired
     * @return
     */
    public ListBoxModel getListBoxModel(String propertySection, String propertyKey, boolean isDummyValueRequired, boolean isDefaultFirstRequired) {
        ListBoxModel model = new ListBoxModel();
        List<String> list = new ArrayList<String>();

        if (propertyKey.equals(BossConstants.DUMMY_VALUE)) {
            list.add(BossConstants.DUMMY_VALUE);
        } else {
            list = getPermittedValues(propertySection, propertyKey, isDummyValueRequired, isDefaultFirstRequired);
        }

        for (String s : list) {
            model.add(String.format("%s", s), s);
        }
        return model;
    }

    /**
     * Get permitted values
     * @param propertySection
     * @param propertyKey
     * @param isDummyValueRequired
     * @param isDefaultFirstRequired
     * @return
     */
    public List<String> getPermittedValues(String propertySection, String propertyKey, boolean isDummyValueRequired, boolean isDefaultFirstRequired) {
        char delimiter = '\n';
        List<String> resultList = new ArrayList<String>();
        resultList = BossUtil.getPropertyValues2(propertySection, propertyKey, delimiter, isDummyValueRequired, isDefaultFirstRequired);

        return resultList;
    }

    /**
     * Populates the drop down list model with the names of the sub-directories from the specified project's staging directory and release
     * with the first item as the dummy value.
     *
     * @param release
     * @return
     */
    public ListBoxModel getDirListBoxModel(String release, String stagingDir) {
        return getDirListBoxModel(release, stagingDir, true);
    }

    /**
     * Populates the drop down list model with the names of the sub-directories from the specified project's staging directory and release.
     *
     * @param release
     * @return
     */
    public ListBoxModel getDirListBoxModel(String release, String stagingDir, boolean isDummyValueNeeded) {
        ListBoxModel model = new ListBoxModel();
        List<String> list = new ArrayList<String>();

        if (isDummyValueNeeded) {
            list.add(BossConstants.DUMMY_VALUE);
        }

        //add the sorted temporary list to the final result list
        list.addAll(getPermittedDirectoryList(release, stagingDir));

        for (String s : list) {
            model.add(String.format("%s", s), s);
        }

        return model;
    }


    /**
     * Gets the list of available directories of the existing build from the staging directory
     * based on the release.
     *
     * @param release
     * @return
     */
    public List<String> getPermittedDirectoryList(String release, String stagingDir) {
        String path2Releases = stagingDir + File.separator + release + File.separator;

        File file = new File(path2Releases);
        File file2 = null;
        List<String> resultList = new ArrayList<String>();

        if (!StringUtils.isBlank(release)) {
            if (file.exists()) {
                if (file.isDirectory()) {
                    String[] list = file.list();
                    for (String s : list) {
                        String path2Build = path2Releases + s;
                        file2 = new File(path2Build);
                        if (file2.isDirectory()) {
                            resultList.add(s);
                        }
                    }
                }
            }

            //sort the result list in reverse order
            Comparator<String> comparator = Collections.reverseOrder();
            Collections.sort(resultList, comparator);
        }

        //return the list containing the results
        return resultList;
    }

    /**
     * SVN list box model
     *
     * @param isDummyValueNeeded
     * @return
     */
    public ListBoxModel getSVNDirListBoxModel(String svnURL,
                                              String svnPath,
                                              String svnUserName,
                                              String svnPassword,
                                              boolean isDummyValueNeeded) {
        ListBoxModel model = new ListBoxModel();
        List<String> list = new ArrayList<String>();

        if (isDummyValueNeeded) {
            list.add(BossConstants.DUMMY_VALUE);
        }

        //add the sorted temporary list to the final result list
        if(!StringUtils.contains(svnPath, BossConstants.DUMMY_VALUE)) {
            list.addAll(getSVNDirectoryList(svnURL, svnPath, svnUserName, svnPassword));
        }

        for (String s : list) {
            model.add(String.format("%s", s), s);
        }
        return model;
    }

    /**
     * SVN directory list
     *
     * @return
     */
    public List<String> getSVNDirectoryList(String svnURL, String svnPath, String svnUserName, String svnPassword) {
        SVNRepositoryFactoryImpl.setup();

        SVNRepository repository = null;
        List<String> resultList = new ArrayList<String>();

        try {
            repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(svnURL));
            ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(svnUserName, svnPassword);
            repository.setAuthenticationManager(authManager);
            Collection entries = repository.getDir(svnPath, -1, null, (Collection) null);
            Iterator iterator = entries.iterator();
            while (iterator.hasNext()) {
                SVNDirEntry dirEntry = (SVNDirEntry) iterator.next();
                if (dirEntry.getKind() == SVNNodeKind.DIR) {
                    String dirName = dirEntry.getName();
                    resultList.add(dirName);
                }
            }
        } catch (SVNException e) {
            LOGGER.log(Level.SEVERE, "Failed to fetch directory list from SVN: " +
                    svnURL + "\n" +
                    svnPath + "\n" +
                    svnUserName + "\n" +
                    svnPassword + "\n", e);
        }

        //sort the result list in reverse order
        Comparator<String> comparator = Collections.reverseOrder();
        Collections.sort(resultList, comparator);

        //return the reverse sorted list
        return resultList;
    }

    /**
     * Get permitted GIS servers
     * @return
     */
    public List<String> getGisPermittedServers() {
        String key = BossConstants.SERVER_KEY;
        char delimiter = '\n';
        boolean isDummyValueRequired = false;
        boolean isDefaultFirstRequired = true;

        List<String> gisPermittedServers = BossUtil.getPropertyValues2(BossConstants.PROJECT_GIS, key, delimiter, isDummyValueRequired, isDefaultFirstRequired);

        return gisPermittedServers;
    }
}
